import { shallowRef, reactive, toRefs, computed } from 'vue'
import { RouterLink } from './router-link'
import { RouterView } from './router-view'

function normalize(record) {
  return {
    path: record.path,
    name: record.name,
    meta: record.meta || {},
    components: {
      default: record.component
    },
    children: record.children || [],
    beforeEnter: record.beforeEnter, // 路由的配置
  }
}


function createMatch(record, parent) {
  const matcher = {
    path: record.path,
    record,
    parent,
    children: []
  }
  if (parent) {
    parent.children.push(matcher)
  }
  return matcher
}

function createRouterMatcher(routes) { // 用户登录后才有的权限  addRoute
  const matchers = []

  function addRoute(record, parent) {
    let normalizedRecord = normalize(record);

    if (parent) {
      normalizedRecord.path = parent.path + '/' + normalizedRecord.path
    }
    const matcher = createMatch(normalizedRecord, parent)
    matchers.push(matcher)
    const children = normalizedRecord.children
    for (let i = 0; i < children.length; i++) {
      addRoute(children[i], matcher)
    }
  }

  function resolve(path) {
    let matched = []
    // 例如我们访问的是 /my/a 组件 -》 /my
    let matcher = matchers.find(matcher => matcher.path === path);

    while (matcher) {
      matched.unshift(matcher.record)// /my  /my/a
      matcher = matcher.parent;
    }

    return {
      path,
      matched
    }
  }

  // 递归添加路由
  routes.forEach(route => addRoute(route))
  return {
    addRoute,
    resolve
  }
}


const START_LOCATION = {
  path: '/',
  matched: [],
  // query params  meta ....
}


export function createRouter({history, routes}) {

  // 路由匹配器： 根据路径进行匹配
  const matcher = createRouterMatcher(routes);
  const currentLocation = shallowRef(START_LOCATION)

  let ready;

  function markReady() {
    if (ready) return;
    ready = true;
    history.listen((to, from) => {
      to = matcher.resolve(to);
      from = currentLocation.value;
      finalNavigation(to, from)
    })
  }

  function finalNavigation(to, from) {
    // 我如何知道 用的事 replace 还是push
    if (from === START_LOCATION) { // 默认就是替换逻辑
      history.replace(to.path)
    } else {
      history.push(to.path)
    }
    currentLocation.value = to; // 更细路径， 更新
    markReady();
  }


  function extractRecords(to, from) {
    const leavingRecords = []
    const updatingRecords = []
    const enteringRecords = []

    let len = Math.max(to.matched.length, from.matched.length); // 获取那个匹配的多，以哪一个为进准
    for (let i = 0; i < len; i++) {
      const fromRecord = from.matched[i];
      if (fromRecord) {
        if (to.matched.find(record => record.path == fromRecord.path)) {
          updatingRecords.push(fromRecord)
        } else {
          leavingRecords.push(fromRecord)
        }
      }
      const toRecord = to.matched[i]
      if (toRecord) {
        if (!from.matched.find(record => record.path === toRecord.path)) {
          enteringRecords.push(toRecord)
        }
      }
    }
    return [
      leavingRecords, updatingRecords, enteringRecords
    ]

  }

  // my -> beforeRouteleave  my/a -> beforeRouteleanve
  function guardToPromise(guard, to, from, record) {
    return () => new Promise((resolve, reject) => { // 返回函数的目的是为了可以组合多个guard
      const next = resolve
      const r = guard.call(record, to, from, next);
      Promise.resolve(r).then(next); // 等待guard 执行完毕后自动调用next
    })
  }

  function extractGuards(guardType, matched, to, from) {
    let guards = []
    for (let record of matched) {
      let comp = record.components.default
      const guard = comp[guardType];
      guard && guards.push(guardToPromise(guard, to, from, record))
    }
    return guards
  }

  function runGuardQueue(guards) {
    return guards.reduce(
      (promise, guard) => promise.then(() => guard()),
      Promise.resolve()
    )
  }

  function navigate(to, from) {
    // to == from 更新
    // to(/my/a) => matched [/my,/my/a] from(/my)  [来]

    const [leavingRecords, updatingRecords, enteringRecords] = extractRecords(to, from);

    // 按照官方文档上的要求 抽离对应的组件的钩子 about里面有beforeRouteleave

    let guards = extractGuards('beforeRouteLeave', leavingRecords.reverse(), to, from)
    return runGuardQueue(guards).then(() => {
      guards = []
      for (let guard of beforeGuards.list()) {
        guards.push(guardToPromise(guard))
      }
      return runGuardQueue(guards); // 全局钩子
    }).then(() => {
      // 更新
      let guards = extractGuards('beforeRouteUpdate', updatingRecords.reverse(), to, from);
      return runGuardQueue(guards);
    }).then(() => {
      guards = []
      for (let record of to.matched) {
        const enterGuard = record.beforeEnter
        if (enterGuard) {
          guards.push(guardToPromise(enterGuard))
        }
      }
      return runGuardQueue(guards);
    }).then(() => {
      guards = extractGuards('beforeRouteEnter', enteringRecords.reverse(), to, from)
      return runGuardQueue(guards);
    }).then(() => {
      guards = []
      for (let guard of beforeResolveGuards.list()) {
        guards.push(guardToPromise(guard))
      }
      return runGuardQueue(guards); // 全局钩子
    })
    // 这里我们需要将函数组合在一起来执行  compose
  }

  function pushWithRedirect(to) {
    const from = currentLocation.value; // 从哪来
    to = matcher.resolve(to.value || to);
    // 有 to 和from 我们就要监控 路径的变化，后续可以更新路径
    // 跳转路由 + 监听
    navigate(to, from).then(() => {
      return finalNavigation(to, from)
    }).then(() => {
      // 在特定的时间触发特定的钩子 来做处理
      for (let guard of afterGuards.list()) {
        guard(to, from)
      }
    })


  }

  function push(to) {
    return pushWithRedirect(to)
  }

  if (currentLocation.value === START_LOCATION) {
    // 这个时候是第一次加载路由，我们需要根据路径找到组件将他放入到START_LOCATION 中
    push(history.location); // 默认的跳转
  }


  function useCallbacks() {
    const hanlders = []; // 用户的函数
    console.log(hanlders)
    const add = (handler) => hanlders.push(handler);
    return {
      add,
      list: () => hanlders
    }
  }


  const beforeGuards = useCallbacks();
  const beforeResolveGuards = useCallbacks();
  const afterGuards = useCallbacks();
  const router = {
    push, // $router.push('/')
    install(app) {
      // 核心注册两个组件
      app.provide('router', router); // 让router link可以拿到push方法进行跳转
      // console.log(currentLocation.value,reactive(currentLocation.value))

      let reactiveObj = {}
      for (let key in START_LOCATION) { // 将每个属性映射成计算属性  + reactive ==  reactive + toRefs
        reactiveObj[key] = computed(() => currentLocation.value[key]);
      }
      app.provide('location', reactive(reactiveObj)); // 根据这个路径和matched 匹配渲染

      app.component('RouterLink', RouterLink)
      app.component('RouterView', RouterView)

      // 让所有的子组件可以获取到路由   provide  globalProperties
    },
    beforeEach: beforeGuards.add,
    beforeResolve: beforeResolveGuards.add,
    afterEach: afterGuards.add
  }

  return router;
}

// redux 
// export default function compose(...funcs: Function[]) {
//     if (funcs.length === 0) {
//       // infer the argument type so it is usable in inference down the line
//       return <T>(arg: T) => arg
//     }

//     if (funcs.length === 1) {
//       return funcs[0]
//     }

//     return funcs.reduce(
//       (a, b) =>
//         (...args: any) =>
//           a(b(...args))
//     )
//   }

// const compose = require('koa-compose');


// promise -> Promise.resolve()
// guard  -> () => promise1
// [() => promise1, () => promise2]

// promise ->  Promise.resolve().then(() => (() => promise1)())
// guard ->  () => promise2

// Promise.resolve().then(() => (() => promise1)()).then(() => (() => promise2)())


// Promise.resolve().then(()=> (()=>promise)()).then(() => (() => promise)()),


// 1） vue路由中有两种方式  hash + h5Api -> h5Api = popstate + pushState 前端路由
// 2) 根据路径找到对应的匹配的组件 进行按照顺序来渲染，为了保证是响应式  shallowRef
// 3) 生命周期钩子 + 将钩子抽离出来 + 转换成promise  + 在组合运行
// 4) addRoute 动态路由添加


// 导航被触发。
// 在失活的组件里调用 beforeRouteLeave 守卫。
// 调用全局的 beforeEach 守卫。
// 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
// 在路由配置里调用 beforeEnter。
// 解析异步路由组件。
// 在被激活的组件里调用 beforeRouteEnter。
// 调用全局的 beforeResolve 守卫(2.5+)。
// 导航被确认。
// 调用全局的 afterEach 钩子。
// 触发 DOM 更新。
// 调用 beforeRouteEnter 守卫中传给 next 的回调函数，创建好的组件实例会作为回调函数的参数传入。